package collectors.email.services;

import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;

import javax.mail.FetchProfile;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.URLName;

import collectors.email.callbacks.GMailCallbacks.ConnectToStore;
import collectors.email.callbacks.GMailCallbacks.FolderToSearch;
import collectors.email.callbacks.GMailCallbacks.MessagesToFetch;
import collectors.email.callbacks.GMailCallbacks.MessagesToRequest;
import collectors.email.callbacks.GMailCallbacks.StoreToFolder;
import collectors.email.models.GmailData;
import collectors.email.models.EmailUser;
import collectors.email.models.GmailEmail;

import org.codehaus.jackson.JsonNode;

import com.google.code.samples.oauth2.OAuth2SaslClientFactory;
import com.sun.mail.gimap.GmailFolder;
import com.sun.mail.gimap.GmailMessage;
import com.sun.mail.gimap.GmailRawSearchTerm;
import com.sun.mail.gimap.GmailSSLStore;

import play.libs.Akka;
import play.libs.F;
import play.libs.F.Function;
import play.libs.F.Promise;
import play.libs.F.Tuple;
import play.libs.WS.Response;
import collectors.email.util.OAuthUtil;

public class GoogleService {

	private static final boolean DEBUG_MAIL = true;

	public static final class OAuth2Provider extends Provider {
		public OAuth2Provider() {
			super("Google OAuth2 Provider", 1.0,
					"Provides the XOAUTH2 SASL Mechanism");
			put("SaslClientFactory.XOAUTH2",
					"com.google.code.samples.oauth2.OAuth2SaslClientFactory");
		}
	}

	public static Promise<EmailUser> storeOrUpdateUser(final Long youId, String code) {
		Promise<Response> tokenResponse = OAuthUtil.getGoogleTokenRequest(code);
		Promise<EmailUser> result = tokenResponse
				.map(new Function<Response, EmailUser>() {
					@Override
					public EmailUser apply(Response a) throws Throwable {
						JsonNode node = a.asJson();
						String encodedIdToken = node.get("id_token")
								.getTextValue().split("\\.")[1];
						JsonNode idToken = OAuthUtil
								.decodeBase64Json(encodedIdToken);
						String email = idToken.get("email").getTextValue();
						String sub = idToken.get("sub").getTextValue();
						String accessToken = node.get("access_token")
								.getTextValue();
						// String refreshToken = node.get("refresh_token")
						// .getTextValue();
						long expiresIn = node.get("expires_in").asLong();

						//TODO find a more reliable way to get the "granted" info from the token
						long now = System.currentTimeMillis();
						GmailData data = GmailData.create(sub, email, accessToken, expiresIn, now);
						EmailUser user = EmailUser.find.byId(youId);
						user.addAccount(data);
						return user;
					}
				});

		return result;
	}

	public static Promise<GmailSSLStore> getGmailStore(String email,
			String oauthToken) {
		Properties props = new Properties();
		props.put("mail.gimaps.sasl.enable", "true");
		props.put("mail.gimaps.sasl.mechanisms", "XOAUTH2");
		props.put("mail.imap.minidletime", "30");
		// props.put("mail.store.protocol", "gimaps");
		props.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken);

		Session session = Session.getInstance(props);
		session.setDebug(DEBUG_MAIL);

		final URLName unusedUrlName = null;
		GmailSSLStore store = new GmailSSLStore(session, unusedUrlName);
		return connectStore(store, email);

	}

	public static Promise<GmailSSLStore> connectStore(
			final GmailSSLStore store, final String email) {
		final String emptyPassword = "";
		return Akka.future(new Callable<GmailSSLStore>() {
			@Override
			public GmailSSLStore call() throws Exception {
				if (!store.isConnected()) {
					store.connect("imap.gmail.com", 993, email, emptyPassword);
				}
				return store;
			}
		});
	}

	public static Promise<GmailFolder> getFolder(GmailSSLStore store,
			String folderName) throws MessagingException {
		// list of special folders:
		// https://developers.google.com/google-apps/gmail/imap_extensions#special-use_extension_of_the_list_command
		final GmailFolder folder = (GmailFolder) store.getFolder(folderName);
		return Akka.future(new Callable<GmailFolder>() {
			@Override
			public GmailFolder call() throws Exception {
				folder.open(Folder.READ_ONLY);
				return folder;
			}
		});
	}

	// needs to be broken in more pieces to take advantage of parallelization
	public static Promise<Tuple<Message[],GmailFolder>> search(final GmailFolder folder,
			final String query) throws MessagingException {

		final GmailRawSearchTerm rawGmailQuery = new GmailRawSearchTerm(query);
		return Akka.future(new Callable<Tuple<Message[],GmailFolder>>() {
			@Override
			public Tuple<Message[],GmailFolder> call() throws Exception {
				Message[] messages = folder.search(rawGmailQuery);
				return F.Tuple(messages, folder);
			}
		});
	}

	public static Promise<Tuple<Message[],GmailFolder>> fetch(final GmailFolder folder,
			final Message[] messageList) throws MessagingException {
		final FetchProfile fp = new FetchProfile();
		fp.add(GmailFolder.FetchProfileItem.THRID);
		fp.add(GmailFolder.FetchProfileItem.MSGID);
		fp.add(GmailFolder.FetchProfileItem.LABELS);
		fp.add(GmailFolder.FetchProfileItem.ENVELOPE);

		return Akka.future(new Callable<Tuple<Message[],GmailFolder>>(){
			@Override
			public Tuple<Message[],GmailFolder> call() throws Exception {
				folder.fetch(messageList, fp);
				return F.Tuple(messageList, folder);
			}});
	}

	public static Promise<List<GmailEmail>> search(String query, int max,
			GmailData data) {
		Promise<GmailSSLStore> gmailStorePromise = getGmailStore(data.email, data.accessToken);
		Promise<List<GmailEmail>> emails = gmailStorePromise
		.flatMap(new ConnectToStore(data.email))
		.flatMap(new StoreToFolder("INBOX"))
		.flatMap(new FolderToSearch(query))
		.flatMap(new MessagesToFetch())
		.flatMap(new MessagesToRequest(max));
		return emails;
	}

	public static Promise<List<GmailEmail>> search(Long gmailUserId,
			String query, int max) {
		EmailUser user = EmailUser.find.byId(gmailUserId);
		GmailData data = user.getGmailData();
 
		return search(query, max, data);
	}
}
